package org.atlantis.grosbeak.pot.transaction;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;

import javax.sql.DataSource;

import org.atlantis.grosbeak.lang.Exceptions;
import org.atlantis.grosbeak.logger.Logger;
import org.atlantis.grosbeak.logger.LoggerFactory;
import org.atlantis.grosbeak.pot.PotException;

/**
 * 事务产生器。
 * 
 * @author @author <a href="mailto:seenoevil.cn@gmail.com">SeeNoEvil</a>
 * 
 */
public class TransactionProvider {

	private Logger logger = LoggerFactory.getLogger(TransactionProvider.class);
	private static ThreadLocal<Connection> connectionTL = new ThreadLocal<Connection>();
	private static final Class<?>[] PROXY = new Class<?>[] { Connection.class };
	private DataSource ds = null;
	private boolean extractConnectionLazy = true;
	private static final String HSQLDB_NAME = "HSQL Database Engine";

	public TransactionProvider() {
	}

	/**
	 * 实例化一个事务产生器的对象。
	 * 
	 * @param ds
	 *            数据源。
	 * @throws PotException
	 */
	public TransactionProvider(DataSource ds) throws PotException {
		setDataSource(ds);
	}

	/**
	 * 设置当前的数据源。
	 * 
	 * @param ds
	 *            数据源。
	 * @throws PotException
	 */
	public void setDataSource(DataSource ds) throws PotException {
		if (ds == null)
			throw Exceptions.makeThrow(PotException.class,
					"DataSource is null.");
		this.ds = ds;
		logger.infoFmt("DataSource is set to: '%s'", ds.toString());
	}

	/**
	 * 设置是否需要延迟开启连接。（即是否需要马上启用Connection对象）。
	 * 
	 * @param extractConnectionLazy
	 */
	public void setExtractConnectionLazy(boolean extractConnectionLazy) {
		this.extractConnectionLazy = extractConnectionLazy;
	}

	/**
	 * 得到当前的Connectio对象。该方法是安全的，因为Connection和线程绑定到一起。
	 * 
	 * @return 当前的Connection对象。
	 * @throws PotException
	 */
	public Connection getCurrentConnection() throws PotException {
		Connection connection = connectionTL.get();
		if (connection != null)
			return connection;
		throw Exceptions
				.makeThrow(PotException.class,
						"Unable to get connection now, because transaction is not begin.");
	}

	/**
	 * 用于延迟创建Connection对象的代理类。
	 * 
	 * @author @author <a href="mailto:seenoevil.cn@gmail.com">SeeNoEvil</a>
	 * 
	 */
	class InvocationHandlerImpl implements InvocationHandler {

		public InvocationHandlerImpl(int level) {
			this.level = level;
		}

		private int level;
		private Connection target = null;

		public Object invoke(Object proxy, Method method, Object[] args)
				throws Throwable {
			if (target == null) {
				String methodName = method.getName();
				if ("commit".equals(methodName)
						|| "rollback".equals(methodName)
						|| "close".equals(methodName)) {
					return null;
				}
				try {
					target = ds.getConnection();
					target.setAutoCommit(false);
					target.setTransactionIsolation(level);
				} catch (SQLException e) {
					logger.warn("Start transaction failed.", e);
					if (target != null) {
						try {
							target.close();
						} catch (SQLException se) {
							logger.warn("Close connection failed.", se);
						}
					}
					throw Exceptions.makeThrow(PotException.class, e,
							"Start transaction failed.");
				}
			}
			return method.invoke(target, args);
		}
	}

	/**
	 * 开启一个事务。
	 * 
	 * @return 开启的事务。
	 * @throws PotException
	 */
	public Transaction begin() throws PotException {
		return begin(Connection.TRANSACTION_READ_COMMITTED);
	}

	public Transaction begin(int level) throws PotException {
		Connection connection = connectionTL.get();
		if (connection != null)
			throw Exceptions.makeThrow(PotException.class,
					"Current transaction has existed and not commit");
		if (extractConnectionLazy) {
			connection = (Connection) Proxy.newProxyInstance(getClass()
					.getClassLoader(), PROXY, new InvocationHandlerImpl(level));
		} else {
			try {
				connection = ds.getConnection();
				connection.setAutoCommit(false);
				connection.setTransactionIsolation(level);
			} catch (SQLException e) {
				logger.warn("Start transaction failed.", e);
				if (connection != null) {
					try {
						connection.close();
					} catch (SQLException se) {
						logger.warn("Close connection failed.", se);
					}
				}
				throw Exceptions.makeThrow(PotException.class, e,
						"Start transaction failed.");
			}
		}
		connectionTL.set(connection);
		return new Transaction(this);
	}

	void clean() throws PotException {
		Connection connection = connectionTL.get();
		if (connection == null)
			throw Exceptions
					.makeThrow(PotException.class,
							"Unable to close the connection after end transaction, because it is null");
		try {
			// HSQLDB数据库，必须执行SHUTDOWN命令后才能把数据持久化到硬盘上
			if (connection.getMetaData().getDatabaseProductName()
					.equalsIgnoreCase(HSQLDB_NAME)) {
				Statement stat = connection.createStatement();
				stat.execute("SHUTDOWN");
				stat.close();
			}
			connection.close();
			logger.debug("Connection closed.");
		} catch (SQLException e) {
			logger.error("Close connection failed.", e);
		}
		connectionTL.remove();
	}
}
